/**
 * Copyright 2013-2025 the original author or authors from the JHipster project.
 *
 * This file is part of the JHipster project, see https://www.jhipster.tech/
 * for more information.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import type { QueuedAdapter } from '@yeoman/types';
import chalk from 'chalk';
import { CheckRepoActions } from 'simple-git';

import BaseGenerator from '../base/index.ts';

import { files } from './files.ts';
import type { Config as GitConfig, GeneratorProperties as GitGeneratorProperties, Options as GitOptions } from './types.ts';

export default class GitGenerator extends BaseGenerator<GitConfig, GitOptions> {
  gitInitialized!: boolean;
  existingRepository!: boolean;
  skipGit!: GitGeneratorProperties['skipGit'];
  readonly forceGit!: GitGeneratorProperties['forceGit'];
  readonly commitMsg!: GitGeneratorProperties['commitMsg'];

  async beforeQueue() {
    if (!this.fromBlueprint) {
      await this.composeWithBlueprints();
    }
  }

  get initializing() {
    return this.asInitializingTaskGroup({
      async checkGit() {
        if (!this.skipGit) {
          const gitInstalled = (await this.createGit().version()).installed;
          if (!gitInstalled) {
            this.log.warn('Git repository will not be created, as Git is not installed on your system');
            this.skipGit = true;
          }
        }
      },
      async initializeMonorepository() {
        if (!this.skipGit && this.jhipsterConfig.monorepository) {
          await this.initializeGitRepository();
        }
      },
    });
  }

  get [BaseGenerator.INITIALIZING]() {
    return this.delegateTasksToBlueprint(() => this.initializing);
  }

  get preparing() {
    return this.asPreparingTaskGroup({
      async preparing() {
        if (!this.skipGit) {
          // Force write .yo-rc.json to disk, it's used to check if the application is regenerated
          this.jhipsterConfig.monorepository ??= undefined;
        }
      },
    });
  }

  get [BaseGenerator.PREPARING]() {
    return this.delegateTasksToBlueprint(() => this.preparing);
  }

  get writing() {
    return this.asWritingTaskGroup({
      async writeFiles() {
        await this.writeFiles({ sections: files, context: {} });
      },
    });
  }

  get [BaseGenerator.WRITING]() {
    return this.delegateTasksToBlueprint(() => this.writing);
  }

  get postWriting() {
    return this.asPostWritingTaskGroup({
      /** Husky commit hook install at install priority requires git to be initialized */
      async initGitRepo() {
        if (!this.skipGit && !this.jhipsterConfig.monorepository) {
          await this.initializeGitRepository();
        }
      },
    });
  }

  get [BaseGenerator.POST_WRITING]() {
    return this.delegateTasksToBlueprint(() => this.postWriting);
  }

  get end() {
    return this.asEndTaskGroup({
      /** Initial commit to git repository after package manager install for package-lock.json */
      async gitCommit() {
        if (this.skipGit) return;
        if (!this.gitInitialized) {
          this.log.warn('The generated application could not be committed to Git, as a Git repository could not be initialized.');
          return;
        }

        const commitFiles = async () => {
          this.debug('Committing files to git');
          const git = this.createGit();
          const repositoryRoot = await git.revparse(['--show-toplevel']);
          const result = await git.log(['-n', '1', '--', '.yo-rc.json']).catch(() => ({ total: 0 }));
          const existingApplication = result.total > 0;
          if (existingApplication && !this.forceGit) {
            this.log.info(
              `Found .yo-rc.json in Git from ${repositoryRoot}. So we assume this is application regeneration. Therefore automatic Git commit is not done. You can do Git commit manually.`,
            );
            return;
          }
          if (!this.forceGit) {
            const statusResult = await git.status();
            if (statusResult.staged.length > 0) {
              this.log.verboseInfo(`The repository ${repositoryRoot} has staged files, skipping commit.`);
              return;
            }
          }
          try {
            let commitMsg =
              this.commitMsg ??
              (existingApplication
                ? `Regenerated ${this.jhipsterConfig.baseName} using generator-jhipster@${this.jhipsterConfig.jhipsterVersion}`
                : `Initial version of ${this.jhipsterConfig.baseName} generated by generator-jhipster@${this.jhipsterConfig.jhipsterVersion}`);
            if (this.jhipsterConfig.blueprints && this.jhipsterConfig.blueprints.length > 0) {
              const bpInfo = this.jhipsterConfig.blueprints.map(bp => `${bp.name}@${bp.version}`).join(', ');
              commitMsg += ` with blueprints ${bpInfo}`;
            }
            await git.add(['.']).commit(commitMsg, { '--allow-empty': null, '--no-verify': null });
            this.log.ok(`Application successfully committed to Git from ${repositoryRoot}.`);
          } catch (e) {
            this.log.warn(
              chalk.red.bold(`Application commit to Git failed from ${repositoryRoot}. Try to commit manually. (${(e as Error).message})`),
            );
          }
        };

        // @ts-expect-error keep compatibility with other adapters
        if (this.env.adapter.queue) {
          await (this.env.adapter as QueuedAdapter).queue(commitFiles);
        } else {
          await commitFiles();
        }
      },
    });
  }

  get [BaseGenerator.END]() {
    return this.delegateTasksToBlueprint(() => this.end);
  }

  async initializeGitRepository() {
    try {
      const git = this.createGit();
      if (await git.checkIsRepo()) {
        if (await git.checkIsRepo(CheckRepoActions.IS_REPO_ROOT)) {
          this.log.info('Using existing git repository.');
        } else {
          this.log.info('Using existing git repository at parent folder.');
        }
        this.existingRepository = true;
      } else if (await git.init()) {
        this.log.ok('Git repository initialized.');
      }
      this.gitInitialized = true;
    } catch (error) {
      this.log.warn(`Failed to initialize Git repository.\n ${error}`);
    }
  }
}
